Skip to content

Commit 1d2ba71

Browse files
committed
Process the result of the product classifier.
* Prompt tweaks
1 parent 3f5e276 commit 1d2ba71

File tree

4 files changed

+68
-38
lines changed

4 files changed

+68
-38
lines changed

kitsune/llm/questions/classifiers.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
topic_prompt,
1313
)
1414
from kitsune.llm.utils import get_llm
15-
16-
# TODO:
17-
# from kitsune.products.utils import get_products, get_taxonomy
18-
from kitsune.products.utils import get_taxonomy
15+
from kitsune.products.utils import get_products, get_taxonomy
1916

2017
DEFAULT_LLM_MODEL = "gemini-2.5-flash-preview-04-17"
2118
HIGH_CONFIDENCE_THRESHOLD = 75
@@ -66,8 +63,7 @@ def handle_spam(payload: dict[str, Any], spam_result: dict[str, Any]) -> dict[st
6663
if not ((action == ModerationAction.SPAM) and spam_result.get("maybe_misclassified")):
6764
return {"action": action, "product_result": {}}
6865

69-
# TODO:
70-
# payload["products"] = get_products(output_format="JSON")
66+
payload["products"] = get_products(output_format="JSON")
7167
product_result = product_classification_chain.invoke(payload)
7268
new_product = product_result.get("product")
7369

kitsune/llm/questions/prompt.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
- `0` = Extremely uncertain.
3030
- `100` = Completely certain.
3131
4. Provide a concise explanation supporting your decision.
32-
5. **Determine if the question may have been classified under the wrong product:** This should be true if and only if the question represents a legitimate support request, **and** the **sole** reason for classifying the question as spam is because it's clearly unrelated to Mozilla's "{product}" product.
32+
5. **Determine if question was misclassified due to wrong product:** True only if this is a legitimate Mozilla support request that
33+
doesn't relate to "{product}" but clearly relates to another Mozilla product.
3334
3435
# Response format
3536
{format_instructions}
@@ -147,7 +148,10 @@
147148
ResponseSchema(
148149
name="maybe_misclassified",
149150
type="bool",
150-
description="A boolean that when true indicates that the question may be classified under the wrong product",
151+
description=(
152+
"True if this appears to be a legitimate Mozilla support request"
153+
" that was flagged as spam solely due to incorrect product categorization."
154+
),
151155
),
152156
)
153157
)

kitsune/products/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22

3-
from django.db.models import Prefetch, Q
43
import yaml
4+
from django.db.models import Prefetch, Q
55

66
from kitsune.products.models import Product, Topic
77

kitsune/questions/utils.py

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -176,41 +176,71 @@ def process_classification_result(
176176
) -> None:
177177
"""
178178
Process the classification result from the LLM and take moderation action.
179+
Handles spam, flag review, and updates to product and topic if suggested by the classifier.
179180
"""
180181
sumo_bot = Profile.get_sumo_bot()
181182
action = result.get("action")
182-
match action:
183-
case ModerationAction.SPAM:
184-
question.mark_as_spam(sumo_bot)
185-
case ModerationAction.FLAG_REVIEW:
183+
184+
if action == ModerationAction.SPAM:
185+
question.mark_as_spam(sumo_bot)
186+
return
187+
elif action == ModerationAction.FLAG_REVIEW:
188+
flag_question(
189+
question,
190+
by_user=sumo_bot,
191+
notes=(
192+
"LLM flagged for manual review, for the following reason:\n"
193+
f"{result.get('spam_result', {}).get('reason', '')}"
194+
),
195+
reason=FlaggedObject.REASON_SPAM,
196+
)
197+
return
198+
199+
product_result = result.get("product_result", {})
200+
topic_result = result.get("topic_result", {})
201+
new_product_title = product_result.get("product")
202+
new_topic_title = topic_result.get("topic")
203+
204+
update_kwargs = {}
205+
206+
if (
207+
new_product_title
208+
and hasattr(question, "product")
209+
and getattr(question.product, "title", None) != new_product_title
210+
):
211+
from kitsune.products.models import Product
212+
213+
try:
214+
new_product = Product.objects.get(title=new_product_title)
215+
except Product.DoesNotExist:
216+
log.warning(
217+
f"LLM suggested product '{new_product_title}' does not exist. Skipping product update."
218+
)
219+
else:
220+
update_kwargs["product"] = new_product
221+
222+
if new_topic_title:
223+
try:
224+
topic = Topic.active.get(title=new_topic_title, visible=True)
225+
except (Topic.DoesNotExist, Topic.MultipleObjectsReturned):
226+
log.warning(
227+
f"LLM suggested topic '{new_topic_title}' is invalid. Skipping topic update."
228+
)
229+
else:
230+
update_kwargs["topic"] = topic
231+
232+
if update_kwargs:
233+
question.save(**update_kwargs)
234+
question.clear_cached_tags()
235+
question.auto_tag()
236+
237+
if update_kwargs.get("topic"):
186238
flag_question(
187239
question,
188240
by_user=sumo_bot,
189241
notes=(
190-
"LLM flagged for manual review, for the following reason:\n"
191-
f"{result['spam_result']['reason']}"
242+
f"LLM classified as {topic.title}, for the following reason:\n"
243+
f"{topic_result.get('reason', '')}"
192244
),
193-
reason=FlaggedObject.REASON_SPAM,
245+
status=FlaggedObject.FLAG_ACCEPTED,
194246
)
195-
case _:
196-
if topic_title := result["topic_result"].get("topic"):
197-
try:
198-
topic = Topic.active.get(title=topic_title, visible=True)
199-
except (Topic.DoesNotExist, Topic.MultipleObjectsReturned):
200-
return
201-
else:
202-
flag_question(
203-
question,
204-
by_user=sumo_bot,
205-
notes=(
206-
"LLM classified as {topic.title}, for the following reason:\n"
207-
f"{result['topic_result']['reason']}"
208-
),
209-
status=FlaggedObject.FLAG_ACCEPTED,
210-
)
211-
if question.topic:
212-
question.tags.remove(question.topic.slug)
213-
question.topic = topic
214-
question.save()
215-
question.tags.add(topic.slug)
216-
question.clear_cached_tags()

0 commit comments

Comments
 (0)